fix(Spanner): Make System Tests Work & Work Faster - #9401
Conversation
a0bfe04 to
6f74d5e
Compare
b6fc922 to
03cee14
Compare
bed320e to
8532688
Compare
8532688 to
8121741
Compare
test(spanner): consolidate test database provisioning fix(spanner): fix PgReadTest index creation on shared database test(spanner): Consolidate DDL operations into test setup suite test(spanner): fix schema mismatches for partitionedDml and PgQueryTest test(spanner): rename PgQueryTest_2 back to PgQueryTest test(spanner): fix PostgreSQL column name mismatches for test tables
test(spanner): Handle DEADLINE_EXCEEDED in BackupTest test(spanner): fix BackupTest timeout and PgQueryTest schema collision test(spanner): add deadline exceeded polling to testCreateBackup2 refactor(spanner): DRY up extended polling loop in BackupTest
…ger range test(spanner): restore seedTable in BatchTest to fix test coverage test(spanner): fix array_rand bug and batch mutations in seedTable test(spanner): fix fundamentally broken partitionRead test logic in BatchTest fix: move seedTable back to its original location fix(cs): fix style issues in BatchTest.php fix(cs): format multi-line args
…tent databases, and apply code review fixes for tests
f321d02 to
60871d1
Compare
| for ($i = 0; $i < 3; $i++) { | ||
| try { | ||
| $backup->create(self::$dbName1, $expireTime); | ||
| break; | ||
| } catch (BadRequestException $e) { | ||
| break; | ||
| } catch (FailedPreconditionException $e) { | ||
| break; | ||
| } catch (\Google\Cloud\Core\Exception\ServiceException $ex) { | ||
| $allowed = [14 /* UNAVAILABLE */, 4 /* DEADLINE_EXCEEDED */]; | ||
| if ($i === 2 || !in_array($ex->getCode(), $allowed)) { | ||
| throw $ex; | ||
| } | ||
| sleep(2); | ||
| } |
There was a problem hiding this comment.
I am having a hard time understanding this. Why are we looping up to 3, but we just break after the first retry?
It seems like we want to catch an exception for $e and then assert that is not null.
What do we need the loop for?
| try { | ||
| $op->cancel(); | ||
| } catch (\Google\Cloud\Core\Exception\ServiceException $e) { | ||
| if ($e->getCode() !== 4 /* DEADLINE_EXCEEDED */) { |
There was a problem hiding this comment.
Not a big fan of "magic numbers"
I believe there is a Code::<CODE_NAME> that is equals to 4 in the protobuf library. Usually using a digit directly can be confusing although I really appreciate the comment explaining what it is!
| self::fullyQualifiedBackupName(self::$backupId1) | ||
| ); | ||
| } catch (ConflictException $e) { | ||
| $this->assertTrue(true); // Expected exception |
There was a problem hiding this comment.
This is a nit but if I recall correctly there is a phpunit function for asserting exceptions:
$this->assertException()
I like using the designated method but I can also see an argument of using the tools to simplify our lives and keep it like it is.
| try { | ||
| foreach (self::$instance->backupOperations() as $op) { | ||
| if (!$op->done()) { | ||
| $metadata = $op->info()['metadata'] ?? []; | ||
| if (isset($metadata['database']) && $metadata['database'] === $dbFullName) { | ||
| try { | ||
| $op->cancel(); | ||
| $op->pollUntilComplete(['maxPollingDurationSeconds' => 120]); | ||
| } catch (\Exception $e) { | ||
| // Ignore exceptions from cancelled operations | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
try {
foreach(self::$instance->backupOperations() as $op) {
if ($op->done()) {
continue;
}
$metadata = $op->info()['metadata'] ?? [];
if (!isset($metadata['database']) || $metadata['database'] !== $dbFullName) {
continue;
}
try {
$op->cancel();
$op->pollUntilComplete();
} catch (\Exception $e) {
//ignore errors
}
}
} catch (\Exception $e) {
//ignore errors
}There was a problem hiding this comment.
trying to one up me on the meme game I see
This PR addresses the ongoing flakiness, high database provisioning overhead, and parallel-execution collisions of the Spanner system tests.
By consolidating test database provisioning, we reduce overall execution time through database reuse. Furthermore, we've removed structural bottlenecks (such as ID collisions and schema mismatches) that prevented these tests from running concurrently. (Parallelizing these tests with
paratestto bring the time down to minutes locally is planned for a subsequent follow-up).Below is a detailed breakdown of the specific problems and fixes implemented in this PR:
1. Test Database Provisioning Overhead & DDL Consolidation
CREATE TABLE), causingTable already existscollisions when tests were executed against a shared persistent database.TestDatabaseManagerto provision a single shared Spanner test database and reuse it across all test classes.SystemTestCaseTrait.php(Standard Spanner) andPgSystemTestCaseTrait.php(Postgres Spanner).CREATE TABLEandDROP TABLEoperations from over a dozen individual test classes (e.g.,BatchTest,WriteTest,PgBatchTest).2. PostgreSQL DDL Compatibility & Schema Mismatches
CREATE TABLEcommands to fail because the tables already existed. Additionally, there was schema drift between individual tests.IF NOT EXISTSto PostgreSQL DDL statements in thePg*Testclasses.PgQueryTestandPgPartitionedDmlTestto align exactly with the unified setup suite definitions.PgReadTest.phpso that secondary index creation operates safely on shared databases without conflicting with concurrent tests.3. Row ID Collisions on Persistent Databases
TransactionTest,OperationsTest,BackupTest) share the sameUserstest table, tests running sequentially on a persistent database were slowly filling up the table. Tests generated random keys using narrow ranges likerand(1, 346464), leading to frequentALREADY_EXISTSerrors during insertion due to the Birthday Paradox.SystemTestCase::randId()generator to use a massive 1-billion key space:rand(1, 999999999).rand()ranges inTransactionTest.phpandPgTransactionTest.phpto use the unifiedself::randId()helper.4.
BackupTestReliability & TimeoutsBackupTestoperations were flaking due to transient errors,DEADLINE_EXCEEDEDerrors when GCP was slow, and improper polling configurations.maxPollingDurationSecondsvia an extended timeout loop for all Backup and Restore operations.pollWithExtendedTimeout($op)helper method inBackupTest.DEADLINE_EXCEEDEDexceptions emitted during intermediate polling attempts.5. Emulator Transaction Leaks
Database::runTransactionto rollback non-single-use active transactions before bubbling up the exception. EnsuredReadTest.phpproperly cleans up local emulator transaction state.6. CI Configuration
phpunit-system.xml.distnow that stability is restored.